Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CONSULT-469] - introduce macro for PGN registration and NmeaMessage type #408

Merged
merged 10 commits into from
Feb 4, 2025

Conversation

gvaradarajan
Copy link
Member

@gvaradarajan gvaradarajan commented Jan 30, 2025

There was some confusion around source id that was cleared up with further testing. It turns out that source_id from some messages and source from the message metadata do not necessarily match (and that some messages do not even have a source_id). This PR contains the subsequent fix

@gvaradarajan gvaradarajan requested a review from acmorrow January 30, 2025 21:12
@gvaradarajan gvaradarajan requested a review from a team as a code owner January 30, 2025 21:12
Copy link
Member

@acmorrow acmorrow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lots of thoughts and questions on this one


impl NmeaMessage {
pub fn new(mut bytes: Vec<u8>) -> Result<Self, NmeaParseError> {
let msg_data = bytes.split_off(32);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This 32 should be a constant somewhere, and replaced globally

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ping?

}

impl NmeaMessage {
pub fn new(mut bytes: Vec<u8>) -> Result<Self, NmeaParseError> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious why this takes the Vec by value rather than just viewing the data, since ultimately, everything gets parsed out, shouldn't this be able to take something like a byte slice, all the way down?

Should this implement TryInto at the top level?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand the first question, but I prefer dealing with a vec rather than a byte slice where I would have to consider the lifetime of a higher level piece of data.

I can make this TryFrom<Vec<u8>>

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do see what you are saying about the virality of lifetime when dealing with byte slices, but, on the other hand, Vec is saying that this method needs a uniquely owned buffer of data in order to convert it into an NmeaMessage. That seems like it shouldn't be needed. If I have a bunch of bytes that I read into a buffer off the wire or bus that is, say, already in a Vec, I'd expect the parser to just be able to consume that data by read-only reference, rather than needing to copy it into a Vec just to call this method. On the other hand, I don't want to delay this work further. Would you be OK with filing a follow-up ticket?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I previously had DataCursor work this way, but then @npmenard said it should own the data. If you can convince him this is better when he gets back, I'll make the ticket.

My opinion on this is not strong but I don't really see a use-case outside of debugging to want a read-only reference to the data meant to be converted. I can see a use case for maybe supporting any type that implements Read. I could make a ticket for that if you agree

@@ -26,11 +29,10 @@ mod tests {
let mut data = Vec::<u8>::new();
let res = general_purpose::STANDARD.decode_vec(water_depth_str, &mut data);
assert!(res.is_ok());
let cursor = DataCursor::new(data[33..].to_vec());
let message = WaterDepth::from_cursor(cursor, 13);
let cursor = DataCursor::new(data[32..].to_vec());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, before, what was happening by starting at byte 33?

Also, is there a reason in this test and the others not to interpret the metadata in the 32 bytes at the front? It seems worthwhile to test that it parses correctly.

Finally, it looks like WaterDepth still does have a source_id, so, I'm curious why the value is no longer being validated.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Byte 32 was the source_id for those messages that have one, but not every message has a source_id field. I can add checks in the tests for the ones that do

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is worth checking the source_id is decoded correctly for those types that have them.

I also think it is worth parsing the metadata here and validating the decoded values.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding the check of the source_id. Did you disagree with the idea to validate the metadata too?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot about that one, I'd prefer to give it its own unit test rather than add it to all of these?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't feel too strongly about it. I was suggesting it mostly because you already have static test data here, but the first 32 bytes get thrown away, when it could be a perfectly good test of decoding the metadata. If you do a separate test then you would just need to obtain the metadata bytes from somewhere anyway. Up to you.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wait I do have a unit test for this actually, does this really matter for every message type?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. Maybe just do one of them, if you want to.

}

impl NmeaMessage {
pub fn new(mut bytes: Vec<u8>) -> Result<Self, NmeaParseError> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do see what you are saying about the virality of lifetime when dealing with byte slices, but, on the other hand, Vec is saying that this method needs a uniquely owned buffer of data in order to convert it into an NmeaMessage. That seems like it shouldn't be needed. If I have a bunch of bytes that I read into a buffer off the wire or bus that is, say, already in a Vec, I'd expect the parser to just be able to consume that data by read-only reference, rather than needing to copy it into a Vec just to call this method. On the other hand, I don't want to delay this work further. Would you be OK with filing a follow-up ticket?

@@ -26,11 +29,10 @@ mod tests {
let mut data = Vec::<u8>::new();
let res = general_purpose::STANDARD.decode_vec(water_depth_str, &mut data);
assert!(res.is_ok());
let cursor = DataCursor::new(data[33..].to_vec());
let message = WaterDepth::from_cursor(cursor, 13);
let cursor = DataCursor::new(data[32..].to_vec());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is worth checking the source_id is decoded correctly for those types that have them.

I also think it is worth parsing the metadata here and validating the decoded values.

@gvaradarajan gvaradarajan requested a review from acmorrow January 31, 2025 20:52
define_pgns!(
(WaterDepth, Pgn128267, 128267),
(TemperatureExtendedRange, Pgn130316, 130316),
(GnssSatsInView, Pgn129540, 129540)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the enum variants looking like Pgn130316 or Pgn129540, this enum is somewhat illegible to a user, since they need to know the PGN code to match, rather than the human readable name like GnssSatsInView. Is it necessary to have the PGN number in the enum variants? Or could they be named much like the wrapped value?

Copy link
Member

@acmorrow acmorrow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is very close to ready to approve. This pass is mostly only small things or questions/suggestions, and some naming nits.

}
_ => {
return Err(error_tokens(
"bits received unexpected attribute value",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pgn instead of bits, I think.

Comment on lines 181 to 182
let metadata = NmeaMessageMetadata::try_from(value)?;
let data = MessageData::from_bytes(metadata.pgn(), msg_data)?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like both these types (NmeaMessageMetadata and MessageData) should either start with Nmea, or not. Given that there is also NmeaMessage, maybe that breaks the tie in favor of NmeaMessageData?

@@ -106,3 +124,101 @@ pub struct GnssSatsInView {
#[length_field = "sats_in_view"]
satellites: Vec<Satellite>,
}

macro_rules! define_pgns {
( $(($pgndef:ident, $enum:ident, $pgn:expr)),* ) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you still need $pgn?

@gvaradarajan gvaradarajan requested a review from acmorrow February 3, 2025 22:12
Copy link
Member

@acmorrow acmorrow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with one last annoying question.

(WaterDepth, Pgn128267, 128267),
(TemperatureExtendedRange, Pgn130316, 130316),
(GnssSatsInView, Pgn129540, 129540)
(WaterDepth, 128267),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Last question, I promise: Do you even need the PGN code as the second argument anymore?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes I cannot use information only available at runtime to generate the content of the match statement for the from_bytes function

@gvaradarajan gvaradarajan merged commit fc41626 into viamrobotics:main Feb 4, 2025
6 checks passed
@acmorrow acmorrow mentioned this pull request Feb 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants